/*
 *  mma8452.c - Linux kernel modules for 3-Axis Orientation/Motion
 *  Detection Sensor 
 *
 *  Copyright (C) 2009-2010 Freescale Semiconductor Ltd.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
#include <linux/hwmon.h>
#include <linux/input-polldev.h>
#include <linux/device.h>
#include <linux/init-input.h>

//#include <mach/system.h>
#include <mach/hardware.h>

#ifdef CONFIG_HAS_EARLYSUSPEND
#include <linux/earlysuspend.h>
#endif

#if defined(CONFIG_HAS_EARLYSUSPEND) || defined(CONFIG_PM)
#include <linux/pm.h>
#endif

/*
 * Defines
 */
#define assert(expr)\
	if (!(expr)) {\
		printk(KERN_ERR "Assertion failed! %s,%d,%s,%s\n",\
			__FILE__, __LINE__, __func__, #expr);\
	}

#define MMA8452_DRV_NAME	"mma8452"
#define SENSOR_NAME             MMA8452_DRV_NAME
#define MMA8452_I2C_ADDR        0x1C
#define MMA8452_ID              0x2A

#define POLL_INTERVAL_MAX       500
#define POLL_INTERVAL           100
#define INPUT_FUZZ              32
#define INPUT_FLAT              32
#define MODE_CHANGE_DELAY_MS    100
#define MMA8452_I2C_ADDR0       0x1C
#define MMA8452_I2C_ADDR1       0x1D

enum {
	DEBUG_INIT = 1U << 0,
	DEBUG_CONTROL_INFO = 1U << 1,
	DEBUG_DATA_INFO = 1U << 2,
	DEBUG_SUSPEND = 1U << 3,
};
static u32 debug_mask = 0;
#define dprintk(level_mask, fmt, arg...)	if (unlikely(debug_mask & level_mask)) \
	printk(KERN_DEBUG fmt , ## arg)

module_param_named(debug_mask, debug_mask, int, 0644);

/* Addresses to scan */
static const unsigned short normal_i2c[2] = {0x1d,I2C_CLIENT_END};
static const unsigned short i2c_address[2] = {MMA8452_I2C_ADDR0,MMA8452_I2C_ADDR1};
static __u32 twi_id = 0;
static int i2c_num = 0;
static struct sensor_config_info gsensor_info = {
	.input_type = GSENSOR_TYPE,
};

#ifdef CONFIG_HAS_EARLYSUSPEND
struct mma8452_data {
	struct i2c_client *client;
	struct early_suspend early_suspend;
};
static struct mma8452_data *mma8452_data;
#endif

/* register enum for mma8452 registers */
enum {
	MMA8452_STATUS = 0x00,
	MMA8452_OUT_X_MSB,
	MMA8452_OUT_X_LSB,
	MMA8452_OUT_Y_MSB,
	MMA8452_OUT_Y_LSB,
	MMA8452_OUT_Z_MSB,
	MMA8452_OUT_Z_LSB,
	
	MMA8452_SYSMOD = 0x0B,
	MMA8452_INT_SOURCE,
	MMA8452_WHO_AM_I,
	MMA8452_XYZ_DATA_CFG,
	MMA8452_HP_FILTER_CUTOFF,
	
	MMA8452_PL_STATUS,
	MMA8452_PL_CFG,
	MMA8452_PL_COUNT,
	MMA8452_PL_BF_ZCOMP,
	MMA8452_PL_P_L_THS_REG,
	
	MMA8452_FF_MT_CFG,
	MMA8452_FF_MT_SRC,
	MMA8452_FF_MT_THS,
	MMA8452_FF_MT_COUNT,

	MMA8452_TRANSIENT_CFG = 0x1D,
	MMA8452_TRANSIENT_SRC,
	MMA8452_TRANSIENT_THS,
	MMA8452_TRANSIENT_COUNT,
	
	MMA8452_PULSE_CFG,
	MMA8452_PULSE_SRC,
	MMA8452_PULSE_THSX,
	MMA8452_PULSE_THSY,
	MMA8452_PULSE_THSZ,
	MMA8452_PULSE_TMLT,
	MMA8452_PULSE_LTCY,
	MMA8452_PULSE_WIND,
	
	MMA8452_ASLP_COUNT,
	MMA8452_CTRL_REG1,
	MMA8452_CTRL_REG2,
	MMA8452_CTRL_REG3,
	MMA8452_CTRL_REG4,
	MMA8452_CTRL_REG5,
	
	MMA8452_OFF_X,
	MMA8452_OFF_Y,
	MMA8452_OFF_Z,
	
	MMA8452_REG_END,
};

enum {
	MODE_2G = 0,
	MODE_4G,
	MODE_8G,
};

/* mma8452 status */
struct mma8452_status {
	u8 mode;
	u8 ctl_reg1;
};

static struct mma8452_status mma_status = {
	.mode 	= 0,
	.ctl_reg1	= 0
};

static struct input_polled_dev *mma8452_idev;
static struct device *hwmon_dev;
static struct i2c_client *mma8452_i2c_client;
static int enable_id = 0;

static struct mutex enable_mutex;
static struct mutex init_mutex;

static void mma8452_resume_events(struct work_struct *work);
static void mma8452_init_events(struct work_struct *work);
static struct workqueue_struct *mma8452_resume_wq;
static struct workqueue_struct *mma8452_init_wq;
static DECLARE_WORK(mma8452_resume_work, mma8452_resume_events);
static DECLARE_WORK(mma8452_init_work, mma8452_init_events);

/**
 * gsensor_detect - Device detection callback for automatic device creation
 * return value:  
 *                    = 0; success;
 *                    < 0; err
 */
static int gsensor_detect(struct i2c_client *client, struct i2c_board_info *info)
{
	struct i2c_adapter *adapter = client->adapter;
	int ret ;

	dprintk(DEBUG_INIT, "%s enter \n", __func__);
	
	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
		return -ENODEV;
    
	if(twi_id == adapter->nr){
		while (i2c_num < 2) {
			client->addr = i2c_address[i2c_num++];
			pr_info("%s: addr= %x\n",__func__,client->addr);
			ret = i2c_smbus_read_byte_data(client, MMA8452_WHO_AM_I);
			if ((ret&0x00FF) == MMA8452_ID) {
				printk( "%s: mma8452 equipment is detected!\n",__func__);
				strlcpy(info->type, SENSOR_NAME, I2C_NAME_SIZE);
				return 0;
			}
		}
		pr_info("%s: mma8452  equipment is not found!\n",__func__);
		return  -ENODEV;    
	} else {
		return -ENODEV;
	}
}

static ssize_t mma8452_enable_store(struct device *dev,
		struct device_attribute *attr,
		const char *buf, size_t count)
{
	unsigned long data;
	int error;

	error = strict_strtoul(buf, 10, &data);
	
	if(error) {
		pr_err("%s strict_strtoul error\n", __FUNCTION__);
		goto exit;
	}

	dprintk(DEBUG_CONTROL_INFO, "%s data = %ld \n", __func__, data);

	if(data) {
		mutex_lock(&init_mutex);
		enable_id = 1;
		mma_status.ctl_reg1 = i2c_smbus_read_byte_data(mma8452_i2c_client, MMA8452_CTRL_REG1);
		mma_status.ctl_reg1 |= 0x01;
		error = i2c_smbus_write_byte_data(mma8452_i2c_client, MMA8452_CTRL_REG1, mma_status.ctl_reg1);
		assert(error==0);
		mutex_unlock(&init_mutex);
	} else {
		mutex_lock(&init_mutex);
		enable_id = 0;
		mma_status.ctl_reg1 = i2c_smbus_read_byte_data(mma8452_i2c_client, MMA8452_CTRL_REG1);
		error = i2c_smbus_write_byte_data(mma8452_i2c_client, MMA8452_CTRL_REG1,mma_status.ctl_reg1 & 0xFE);
		assert(error==0);
		mutex_unlock(&init_mutex);
	}

	return count;

exit:
	return error;
}

static ssize_t mma8452_delay_store(struct device *dev,struct device_attribute *attr,
		const char *buf, size_t count)
{
   unsigned long data;
	int error;

	error = strict_strtoul(buf, 10, &data);
	if (error)
		return error;
	if (data > POLL_INTERVAL_MAX)
		data = POLL_INTERVAL_MAX;
	mma8452_idev->poll_interval = data;

	return count;
		}

static DEVICE_ATTR(enable, 0664,
		NULL, mma8452_enable_store);
		
static DEVICE_ATTR(delay, 0664,
		NULL, mma8452_delay_store);

static struct attribute *mma8452_attributes[] = {
	&dev_attr_enable.attr,
	&dev_attr_delay.attr,
	NULL
};

static struct attribute_group mma8452_attribute_group = {
	.attrs = mma8452_attributes
};
/***************************************************************
 *
 * Initialization function
 */
static int mma8452_init_client(struct i2c_client *client)
{
	int result;

	dprintk(DEBUG_INIT, "mma8452 device init\n");

	mutex_lock(&init_mutex);
	mma_status.ctl_reg1 = 0x20;
	result = i2c_smbus_write_byte_data(client, MMA8452_CTRL_REG1, mma_status.ctl_reg1);
	assert(result==0);
		
	mma_status.mode	= MODE_2G;
	result = i2c_smbus_write_byte_data(client, MMA8452_XYZ_DATA_CFG, mma_status.mode);
	assert(result==0);
		
	mma_status.ctl_reg1 |= 0x01;
	result = i2c_smbus_write_byte_data(client, MMA8452_CTRL_REG1, mma_status.ctl_reg1);
	assert(result==0);
	mutex_unlock(&init_mutex);
	
	msleep(MODE_CHANGE_DELAY_MS);

	dprintk(DEBUG_INIT, "mma8452 device init end\n");

	return result;
}

/***************************************************************
*
* read sensor data from mma8452
*
***************************************************************/ 				
static int mma8452_read_data(short *x, short *y, short *z) {
	u8	tmp_data[7];



	if (i2c_smbus_read_i2c_block_data(mma8452_i2c_client,MMA8452_OUT_X_MSB,7,tmp_data) < 7) {
		dev_err(&mma8452_i2c_client->dev, "i2c block read failed\n");
		return -3;
	}

	*x = ((tmp_data[0] << 8) & 0xff00) | tmp_data[1];
	*y = ((tmp_data[2] << 8) & 0xff00) | tmp_data[3];
	*z = ((tmp_data[4] << 8) & 0xff00) | tmp_data[5];

	*x = (short)(*x) >> 4;
	*y = (short)(*y) >> 4;
	*z = (short)(*z) >> 4;


	if (mma_status.mode == MODE_4G){
		(*x)=(*x)<<1;
		(*y)=(*y)<<1;
		(*z)=(*z)<<1;
	}
	else if (mma_status.mode == MODE_8G){
		(*x)=(*x)<<2;
		(*y)=(*y)<<2;
		(*z)=(*z)<<2;
	}

	return 0;
}

static void report_abs(void)
{
	short x,y,z;
	int result;

	result = i2c_smbus_read_byte_data(mma8452_i2c_client, MMA8452_STATUS);
	if (!(result & 0x08)) {
		dprintk(DEBUG_DATA_INFO, "mma8452 check new data\n");
		return;		/* wait for new data */
	}

	if (mma8452_read_data(&x,&y,&z) != 0) {
		dprintk(DEBUG_DATA_INFO, "mma8452 no data");
		return;
	}

	dprintk(DEBUG_DATA_INFO, "x= 0x%hx, y = 0x%hx, z = 0x%hx\n", x, y, z);
	input_report_abs(mma8452_idev->input, ABS_X, x);
	input_report_abs(mma8452_idev->input, ABS_Y, y);
	input_report_abs(mma8452_idev->input, ABS_Z, z);
	input_sync(mma8452_idev->input);
}

static void mma8452_dev_poll(struct input_polled_dev *dev)
{
	report_abs();
} 

static void mma8452_resume_events (struct work_struct *work)
{
	int result;

	mutex_lock(&init_mutex);
	mma_status.ctl_reg1 = 0x20;
	result = i2c_smbus_write_byte_data(mma8452_i2c_client, MMA8452_CTRL_REG1, mma_status.ctl_reg1);
	assert(result==0);

	mma_status.mode	= MODE_2G;
	result = i2c_smbus_write_byte_data(mma8452_i2c_client, MMA8452_XYZ_DATA_CFG, mma_status.mode);
	assert(result==0);

	if (1 == enable_id) {
		mma_status.ctl_reg1 |= 0x01;
		result = i2c_smbus_write_byte_data(mma8452_i2c_client, MMA8452_CTRL_REG1, mma_status.ctl_reg1);
		assert(result==0);
	}
	mutex_unlock(&init_mutex);

	msleep(MODE_CHANGE_DELAY_MS);
	mutex_lock(&enable_mutex);
	if (mma8452_idev->input->users)
		mma8452_idev->input->open(mma8452_idev->input);
	mutex_unlock(&enable_mutex);
}

#ifdef CONFIG_HAS_EARLYSUSPEND	
static void mma8452_early_suspend(struct early_suspend *h)
{
	int result;

	dprintk(DEBUG_SUSPEND, "mma8452 early suspend");

	flush_workqueue(mma8452_resume_wq);
	
	mutex_lock(&enable_mutex);
	if (mma8452_idev->input->users)
		mma8452_idev->input->close(mma8452_idev->input);
	mutex_unlock(&enable_mutex);

	mutex_lock(&init_mutex);
	mma_status.ctl_reg1 = i2c_smbus_read_byte_data(mma8452_i2c_client, MMA8452_CTRL_REG1);
	result = i2c_smbus_write_byte_data(mma8452_i2c_client, MMA8452_CTRL_REG1,mma_status.ctl_reg1 & 0xFE);
	assert(result==0);
	mutex_unlock(&init_mutex);

	dprintk(DEBUG_SUSPEND, "mma8452 early suspend end");
	return ;
}

static void mma8452_late_resume(struct early_suspend *h) //(struct i2c_client *client)
{
	int result;

	dprintk(DEBUG_SUSPEND, "mma8452 late resume");

	if (NORMAL_STANDBY == standby_type) {
		mutex_lock(&init_mutex);
		if (1 == enable_id) {
			mma_status.ctl_reg1 = i2c_smbus_read_byte_data(mma8452_i2c_client, MMA8452_CTRL_REG1);
			mma_status.ctl_reg1 |= 0x01;
			result = i2c_smbus_write_byte_data(mma8452_i2c_client, MMA8452_CTRL_REG1, mma_status.ctl_reg1);
			assert(result==0);
		}
		mutex_unlock(&init_mutex);

		mutex_lock(&enable_mutex);
		if (mma8452_idev->input->users)
			mma8452_idev->input->open(mma8452_idev->input);
		mutex_unlock(&enable_mutex);
	} else if (SUPER_STANDBY == standby_type) {
		queue_work(mma8452_resume_wq, &mma8452_resume_work);
	}

	dprintk(DEBUG_SUSPEND, "mma8452 late resume end");
	return ;
}
#else
#ifdef CONFIG_PM
static int mma8452_resume(struct i2c_client *client)
{
	int result;

	dprintk(DEBUG_SUSPEND, "mma8452 resume");
	
	if (NORMAL_STANDBY == standby_type) {
		mutex_lock(&init_mutex);
		if (1 == enable_id) {
			mma_status.ctl_reg1 = i2c_smbus_read_byte_data(mma8452_i2c_client, MMA8452_CTRL_REG1);
			mma_status.ctl_reg1 |= 0x01;
			result = i2c_smbus_write_byte_data(mma8452_i2c_client, MMA8452_CTRL_REG1, mma_status.ctl_reg1);
			assert(result==0);
		}
		mutex_unlock(&init_mutex);

		mutex_lock(&enable_mutex);
		if (mma8452_idev->input->users)
			mma8452_idev->input->open(mma8452_idev->input);
		mutex_unlock(&enable_mutex);
	} else if (SUPER_STANDBY == standby_type) {
		queue_work(mma8452_resume_wq, &mma8452_resume_work);
	}

	dprintk(DEBUG_SUSPEND, "mma8452 resume end");
	return 0;
}

static int mma8452_suspend(struct i2c_client *client, pm_message_t mesg)
{
	int result;

	dprintk(DEBUG_SUSPEND, "mma8452 suspend");

	flush_workqueue(mma8452_resume_wq);
	
	mutex_lock(&enable_mutex);
	if (mma8452_idev->input->users)
		mma8452_idev->input->close(mma8452_idev->input);
	mutex_unlock(&enable_mutex);

	mutex_lock(&init_mutex);
	if (1 == enable_id) {
		mma_status.ctl_reg1 = i2c_smbus_read_byte_data(mma8452_i2c_client, MMA8452_CTRL_REG1);
		result = i2c_smbus_write_byte_data(mma8452_i2c_client, MMA8452_CTRL_REG1,mma_status.ctl_reg1 & 0xFE);
		assert(result==0);
	}
	mutex_unlock(&init_mutex);

	dprintk(DEBUG_SUSPEND, "mma8452 suspend end");
	return 0;
}
#endif
#endif

static void mma8452_init_events (struct work_struct *work)
{
	mma8452_init_client(mma8452_i2c_client);
}

static int mma8452_probe(struct i2c_client *client,
				   const struct i2c_device_id *id)
{
	int result;
	struct input_dev *idev;
	struct i2c_adapter *adapter;

	dprintk(DEBUG_INIT, "mma8452 probe i2c address is %d \n",i2c_address[i2c_num-1]);
	client->addr =i2c_address[i2c_num-1];
 
	mma8452_i2c_client = client;
	adapter = to_i2c_adapter(client->dev.parent);
	result = i2c_check_functionality(adapter,
					 I2C_FUNC_SMBUS_BYTE |
					 I2C_FUNC_SMBUS_BYTE_DATA);
	assert(result);

	mutex_init(&init_mutex);
	mutex_init(&enable_mutex);

	mma8452_init_wq = create_singlethread_workqueue("mma8452_init");
	if (mma8452_init_wq == NULL) {
		printk("create mma8452_init_wq fail!\n");
		return -ENOMEM;
	}

	/* Initialize the MMA8452 chip */
	queue_work(mma8452_init_wq, &mma8452_init_work);

	hwmon_dev = hwmon_device_register(&client->dev);
	assert(!(IS_ERR(hwmon_dev)));

	dev_info(&client->dev, "build time %s %s\n", __DATE__, __TIME__);
  

	/*input poll device register */
	mma8452_idev = input_allocate_polled_device();
	if (!mma8452_idev) {
		dev_err(&client->dev, "alloc poll device failed!\n");
		result = -ENOMEM;
		return result;
	}
	mma8452_idev->poll = mma8452_dev_poll;
	mma8452_idev->poll_interval = POLL_INTERVAL;
	mma8452_idev->poll_interval_max = POLL_INTERVAL_MAX;
	idev = mma8452_idev->input;
	idev->name = MMA8452_DRV_NAME;
	idev->id.bustype = BUS_I2C;
	idev->evbit[0] = BIT_MASK(EV_ABS);

	input_set_abs_params(idev, ABS_X, -8192, 8191, INPUT_FUZZ, INPUT_FLAT);
	input_set_abs_params(idev, ABS_Y, -8192, 8191, INPUT_FUZZ, INPUT_FLAT);
	input_set_abs_params(idev, ABS_Z, -8192, 8191, INPUT_FUZZ, INPUT_FLAT);
	result = input_register_polled_device(mma8452_idev);
	mma8452_idev->input->close(mma8452_idev->input);
	if (result) {
		dev_err(&client->dev, "register poll device failed!\n");
		return result;
	}
	result = sysfs_create_group(&mma8452_idev->input->dev.kobj, &mma8452_attribute_group);

	mma8452_resume_wq = create_singlethread_workqueue("mma8452_resume");
	if (mma8452_resume_wq == NULL) {
		printk("create mma8452_resume_wq fail!\n");
		return -ENOMEM;
	}

#ifdef CONFIG_HAS_EARLYSUSPEND	
	dprintk(DEBUG_INIT, "==register_early_suspend =\n");
	mma8452_data = kzalloc(sizeof(*mma8452_data), GFP_KERNEL);
	if (mma8452_data == NULL) {
		result = -ENOMEM;
		goto err_alloc_data_failed;
	}

	mma8452_data->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 3;	
	mma8452_data->early_suspend.suspend = mma8452_early_suspend;
	mma8452_data->early_suspend.resume	= mma8452_late_resume;
	register_early_suspend(&mma8452_data->early_suspend);
#endif
	dprintk(DEBUG_INIT, "mma8452 probe end\n");
	return result;
#ifdef CONFIG_HAS_EARLYSUSPEND
err_alloc_data_failed:
#endif
	return result;
}

static int mma8452_remove(struct i2c_client *client)
{
	int result;
	mutex_lock(&init_mutex);
	mma_status.ctl_reg1 = i2c_smbus_read_byte_data(client, MMA8452_CTRL_REG1);
	result = i2c_smbus_write_byte_data(client, MMA8452_CTRL_REG1,mma_status.ctl_reg1 & 0xFE);
	assert(result==0);
	mutex_unlock(&init_mutex);

#ifdef CONFIG_HAS_EARLYSUSPEND	
	unregister_early_suspend(&mma8452_data->early_suspend);	
#endif
	cancel_work_sync(&mma8452_resume_work);
	destroy_workqueue(mma8452_resume_wq);
	sysfs_remove_group(&mma8452_idev->input->dev.kobj, &mma8452_attribute_group);
	mma8452_idev->input->close(mma8452_idev->input);
	input_unregister_polled_device(mma8452_idev);
	input_free_polled_device(mma8452_idev);
	hwmon_device_unregister(hwmon_dev);
	cancel_work_sync(&mma8452_init_work);
	destroy_workqueue(mma8452_init_wq);
	i2c_set_clientdata(client, NULL);

	return result;
}


static const struct i2c_device_id mma8452_id[] = {
	{ MMA8452_DRV_NAME, 0 },
	{ }
};
MODULE_DEVICE_TABLE(i2c, mma8452_id);

static struct i2c_driver mma8452_driver = {
	.class = I2C_CLASS_HWMON,
	.driver = {
	.name	= MMA8452_DRV_NAME,
	.owner	= THIS_MODULE,
	},
	
	.probe	= mma8452_probe,
	.remove	= mma8452_remove,
#ifdef CONFIG_HAS_EARLYSUSPEND
#else
#ifdef CONFIG_PM
	.suspend = mma8452_suspend,
	.resume = mma8452_resume,
#endif
#endif
	.id_table = mma8452_id,
	.detect = gsensor_detect,
	.address_list	= normal_i2c,
};

static int __init mma8452_init(void)
{
	/* register driver */
	int res;
	dprintk(DEBUG_INIT, "======%s=========. \n", __func__);
	if (input_fetch_sysconfig_para(&(gsensor_info.input_type))) {
		printk("%s: err.\n", __func__);
		return -1;
	} else
		twi_id = gsensor_info.twi_id;

	dprintk(DEBUG_INIT, "%s i2c twi is %d \n", __func__, twi_id);

	res = i2c_add_driver(&mma8452_driver);
	if (res < 0) {
		printk(KERN_INFO "add mma8452 i2c driver failed\n");
		return -ENODEV;
	}
	dprintk(DEBUG_INIT, "add mma8452 i2c driver\n");

	return (res);
}

static void __exit mma8452_exit(void)
{
	dprintk(DEBUG_INIT, "remove mma8452 i2c driver.\n");
	i2c_del_driver(&mma8452_driver);
}

MODULE_AUTHOR("Chen Gang <gang.chen@freescale.com>");
MODULE_DESCRIPTION("MMA8452 3-Axis Orientation/Motion Detection Sensor driver");
MODULE_LICENSE("GPL");
MODULE_VERSION("1.1");

module_init(mma8452_init);
module_exit(mma8452_exit);

